﻿using System;
using System.ComponentModel;
using System.Runtime.Serialization;

using SharpGL.Media3D;
using SharpGL.SceneGraph.Helpers;

namespace SharpGL.SceneGraph.Transformations
{
    /// <summary>
    /// This enumeration describes the linear transformation order.
    /// </summary>
    [DataContract()]
    public enum LinearTransformationOrder
    {
        /// <summary>
        /// Translate > Rotate > Scale
        /// </summary>
        [EnumMember()]
        TranslateRotateScale = 0,

        /// <summary>
        /// Rotate > Translate > Scale
        /// </summary>
        [EnumMember()]
        RotateTranslateScale = 1,
    }

    /// <summary>
    /// The LinearTransformation class represents a linear transformation, such
    /// as a transformation that moves us from world space into object space.
    /// </summary>
    [TypeConverter(typeof(ExpandableObjectConverter))]
    [DataContract(/*IsReference = true, */Name = "LinearTransformation", Namespace = "SharpGL.SceneGraph")]
    public class LinearTransformation : Transformation, ICloneable
    {
        #region Private Consts

        private static readonly float defaultTranslateX = 0;
        private static readonly float defaultTranslateY = 0;
        private static readonly float defaultTranslateZ = 0;
        
        private static readonly float defaultRotateX = 0;
        private static readonly float defaultRotateY = 0;
        private static readonly float defaultRotateZ = 0;
        
        private static readonly float defaultScaleX = 1;
        private static readonly float defaultScaleY = 1;
        private static readonly float defaultScaleZ = 1;
        
        private static readonly LinearTransformationOrder defaultTransformationOrder = LinearTransformationOrder.TranslateRotateScale;

        #endregion Private Consts

        #region Private Fields

        /// <summary>
        /// X Component of the Translation.
        /// </summary>
        [DataMember()]
        private float translateX = 0;

        /// <summary>
        /// Y Component of the Translation.
        /// </summary>
        [DataMember()]
        private float translateY = 0;

        /// <summary>
        /// Z Component of the Translation.
        /// </summary>
        [DataMember()]
        private float translateZ = 0;

        /// <summary>
        /// X Component of the Rotation.
        /// </summary>
        [DataMember()]
        private float rotateX = 0;

        /// <summary>
        /// Y Component of the Rotation.
        /// </summary>
        [DataMember()]
        private float rotateY = 0;

        /// <summary>
        /// Z Component of the Rotation.
        /// </summary>
        [DataMember()]
        private float rotateZ = 0;

        /// <summary>
        /// X Component of the Scale.
        /// </summary>
        [DataMember()]
        private float scaleX = 1;
        
        /// <summary>
        /// Y Component of the Scale.
        /// </summary>
        [DataMember()]
        private float scaleY = 1;

        /// <summary>
        /// Z Component of the Scale.
        /// </summary>
        [DataMember()]
        private float scaleZ = 1;

        /// <summary>
        /// The order of the linear transformation.
        /// </summary>
        [DataMember()]
        private LinearTransformationOrder transformationOrder = LinearTransformationOrder.TranslateRotateScale;

        #endregion  Private Fields

        #region Public Constructors

        public LinearTransformation()
        {
            translateX = defaultTranslateX;
            translateY = defaultTranslateY;
            translateZ = defaultTranslateZ;
            
            rotateX = defaultRotateX;
            rotateY = defaultRotateY;
            rotateZ = defaultRotateZ;
            
            scaleX = defaultScaleX;
            scaleY = defaultScaleY;
            scaleZ = defaultScaleZ;
            
            transformationOrder = defaultTransformationOrder;
        }

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Performs the transformation on the current matrix.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        public override void Transform(OpenGL gl)
        {
            if (OpenGL.IsValidContext(gl))
            {
                //  Perform the transformation in the specified order.
                switch (transformationOrder)
                {
                    case LinearTransformationOrder.TranslateRotateScale:
                        gl.Translate(translateX, translateY, translateZ);
                        gl.Rotate(rotateX, rotateY, rotateZ);
                        gl.Scale(scaleX, scaleY, scaleZ);
                        break;
                    case LinearTransformationOrder.RotateTranslateScale:
                        gl.Rotate(rotateX, rotateY, rotateZ);
                        gl.Translate(translateX, translateY, translateZ);
                        gl.Scale(scaleX, scaleY, scaleZ);
                        break;
                    default:
                        break;
                }
            }
        }

        /// <summary>
        /// Reset to default state.
        /// </summary>
        public override void Reset()
        {
            translateX = defaultTranslateX;
            translateY = defaultTranslateY;
            translateZ = defaultTranslateZ;
            
            rotateX = defaultRotateX;
            rotateY = defaultRotateY;
            rotateZ = defaultRotateZ;
            
            scaleX = defaultScaleX;
            scaleY = defaultScaleY;
            scaleZ = defaultScaleZ;
            
            transformationOrder = defaultTransformationOrder;
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// a new object that is a copy of this instance.
        /// </returns>
        public virtual object Clone()
        {
            // Clone the object.
            return new LinearTransformation()
            {
                translateX = this.translateX,
                translateY = this.translateY,
                translateZ = this.translateZ,
                
                rotateX = this.rotateX,
                rotateY = this.rotateY,
                rotateZ = this.rotateZ,
                
                scaleX = this.scaleX,
                scaleY = this.scaleY,
                scaleZ = this.scaleZ,
                
                transformationOrder = this.transformationOrder
            };
        }

        #endregion Public Methods

        #region Public Properties

        /// <summary>
        /// Gets the translation point.
        /// </summary>
        [Browsable(false)]
        public Vector3D TranslationVector
        {
            get
            {
                return new Vector3D(translateX, translateY, translateZ);
            }
            set
            {
                TranslateX = value.X;
                TranslateY = value.Y;
                TranslateZ = value.Z;
            }
        }

        /// <summary>
        /// Gets or sets the x component of the translation.
        /// </summary>
        /// <value>
        /// The x component of the translation.
        /// </value>
        [Category("Transformation"), Description("The X Component of the Translation")]
        public float TranslateX
        {
            get { return translateX; }
            set
            {
                if (translateX != value)
                {
                    translateX = value;

                    NotifyPropertyChanged("TranslateX");
                }
            }
        }

        /// <summary>
        /// Gets or sets the y component of the translation.
        /// </summary>
        /// <value>
        /// The y component of the translation.
        /// </value>
        [Category("Transformation"), Description("The Y Component of the Translation")]
        public float TranslateY
        {
            get { return translateY; }
            set
            {
                if (translateY != value)
                {
                    translateY = value;

                    NotifyPropertyChanged("TranslateY");
                }
            }
        }

        /// <summary>
        /// Gets or sets the z component of the translation.
        /// </summary>
        /// <value>
        /// The z component of the translation.
        /// </value>
        [Category("Transformation"), Description("The Z Component of the Translation")]
        public float TranslateZ
        {
            get { return translateZ; }
            set
            {
                if (translateZ != value)
                {
                    translateZ = value;

                    NotifyPropertyChanged("TranslateZ");
                }
            }
        }

        /// <summary>
        /// Gets or sets the x component of the rotation.
        /// </summary>
        /// <value>
        /// The x component of the rotation.
        /// </value>
        [Category("Transformation"), Description("Rotation around the X axis (in degrees)")]
        public float RotateX
        {
            get { return rotateX; }
            set
            {
                if (rotateX != value)
                {
                    rotateX = value;

                    NotifyPropertyChanged("RotateX");
                }
            }
        }

        /// <summary>
        /// Gets or sets the y component of the rotation.
        /// </summary>
        /// <value>
        /// The y component of the rotation.
        /// </value>
        [Category("Transformation"), Description("Rotation around the Y axis (in degrees)")]
        public float RotateY
        {
            get { return rotateY; }
            set
            {
                if (rotateY != value)
                {
                    rotateY = value;

                    NotifyPropertyChanged("RotateY");
                }
            }
        }

        /// <summary>
        /// Gets or sets the z component of the rotation.
        /// </summary>
        /// <value>
        /// The z component of the rotation.
        /// </value>
        [Category("Transformation"), Description("Rotation around the Z axis (in degrees)")]
        public float RotateZ
        {
            get { return rotateZ; }
            set
            {
                if (rotateZ != value)
                {
                    rotateZ = value;

                    NotifyPropertyChanged("RotateZ");
                }
            }
        }

        /// <summary>
        /// Gets or sets the x component of the scale.
        /// </summary>
        /// <value>
        /// The x component of the scale.
        /// </value>
        [Category("Transformation"), Description("X Component of the Scaling")]
        public float ScaleX
        {
            get { return scaleX; }
            set
            {
                if (scaleX != value)
                {
                    scaleX = value;

                    NotifyPropertyChanged("ScaleX");
                }
            }
        }

        /// <summary>
        /// Gets or sets the y component of the scale.
        /// </summary>
        /// <value>
        /// The y component of the scale.
        /// </value>
        [Category("Transformation"), Description("Y Component of the Scaling")]
        public float ScaleY
        {
            get { return scaleY; }
            set
            {
                if (scaleY != value)
                {
                    scaleY = value;

                    NotifyPropertyChanged("ScaleY");
                }
            }
        }

        /// <summary>
        /// Gets or sets the z component of the scale.
        /// </summary>
        /// <value>
        /// The z component of the scale.
        /// </value>
        [Category("Transformation"), Description("Z Component of the Scaling")]
        public float ScaleZ
        {
            get { return scaleZ; }
            set
            {
                if (scaleZ != value)
                {
                    scaleZ = value;

                    NotifyPropertyChanged("ScaleZ");
                }
            }
        }

        /// <summary>
        /// Gets or sets the transformation order.
        /// </summary>
        /// <value>
        /// The transformation order.
        /// </value>
        [Category("Transformation"), Description("Transformation order")]
        public LinearTransformationOrder TransformationOrder
        {
            get { return transformationOrder; }
            set
            {
                if (transformationOrder != value)
                {
                    transformationOrder = value;

                    NotifyPropertyChanged("TransformationOrder");
                }
            }
        }

        /// <summary>
        /// Gets the transformation matrix.
        /// </summary>
        /// <value>
        /// The transformation matrix.
        /// </value>
        [Browsable(false)]
        public override Matrix TransformationMatrix
        {
            get
            {
                switch (transformationOrder)
                {
                    case LinearTransformationOrder.RotateTranslateScale:
                            return TransformationHelper.RotateXMatrix(rotateX) *
                                TransformationHelper.RotateYMatrix(rotateY) *
                                TransformationHelper.RotateZMatrix(rotateZ) *
                                TransformationHelper.TranslateMatrix(translateX, translateY, translateZ) *
                                TransformationHelper.ScaleMatrix(scaleX, scaleY, scaleZ);

                    case LinearTransformationOrder.TranslateRotateScale:
                            return TransformationHelper.TranslateMatrix(translateX, translateY, translateZ) * 
                                TransformationHelper.RotateXMatrix(rotateX) *
                                TransformationHelper.RotateYMatrix(rotateY) *
                                TransformationHelper.RotateZMatrix(rotateZ) *
                                TransformationHelper.ScaleMatrix(scaleX, scaleY, scaleZ);
                    default:
                            return new Matrix(Matrix.Identity(4));
                }
            }
        }

        #endregion Public Properties
    }
}
